-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
config: Struct opaque filter proto config support, LDS/RDS integratio… #1495
Conversation
…n test. This PR provides an integration test demoing LDS/RDS/CDS/EDS. Along the way, needed to solve the problem of plumbing in the new HTTP connection manager filter proto config, rather than the legacy v1 JSON. The solution adopted in this PR is to use a "deprecatedV1" field in the opaque config to determine if we're doing legacy config or v2 proto.
@mattklein123 I think this is actually cleaner, if you look at the example configs. There's no need for the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah configs look very readable to me. +1. Will do full review when passing tests.
@@ -207,6 +207,13 @@ class IntegrationTestServer : Logger::Loggable<Logger::Id::testing>, | |||
} | |||
void start(const Network::Address::IpVersion version); | |||
void start(); | |||
|
|||
void waitForCounterGe(const std::string& name, uint64_t value) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know there are other cases where we are doing this (In fact IIRC you implemented one at some point, and I think there may be others)? I might be making this up. If there are can we replace with this?
source/common/protobuf/utility.h
Outdated
@@ -48,10 +49,26 @@ class RepeatedPtrUtil { | |||
class MessageUtil { | |||
public: | |||
static std::size_t hash(const Protobuf::Message& message) { | |||
return std::hash<std::string>{}(message.SerializeAsString()); | |||
std::string text; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Out of curiosity what was wrong with the old version? Maybe a small comment on what all of this is for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unrelated, but it was causing test flakes, since the non-deterministic proto serialization led to distinct hashes for the same proto.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK makes sense. Maybe just a small comment?
source/common/protobuf/utility.h
Outdated
} | ||
|
||
static void loadFromJson(const std::string json, Protobuf::Message& message); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the reason we are passing std::string here vs reference string issues again? If so can we put a TODO somewhere?
@@ -58,6 +58,7 @@ typedef std::unique_ptr<ListenerImpl> ListenerImplPtr; | |||
COUNTER(listener_added) \ | |||
COUNTER(listener_modified) \ | |||
COUNTER(listener_removed) \ | |||
COUNTER(listener_create_success) \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this needs to get added to docs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few more random comments and some questions. I'm a little confused.
include/envoy/server/filter_config.h
Outdated
} | ||
|
||
/** | ||
* @return std::unique_ptr<Protobuf::Message> create empty config proto message for v2. The filter |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: worth defining Protobuf::MessagePtr? I think we will have a lot of this.
source/common/protobuf/protobuf.h
Outdated
@@ -4,8 +4,10 @@ | |||
#include <string> | |||
#include <vector> | |||
|
|||
#include "google/protobuf/any.pb.h" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We still using any?
NetworkFilterFactoryCb HttpConnectionManagerFilterConfigFactory::createFilterFactoryFromProto( | ||
const Protobuf::Message& config, FactoryContext& context) { | ||
return createHttpConnectionManagerFilterFactory( | ||
dynamic_cast<const envoy::api::v2::filter::HttpConnectionManager&>(config), context); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to check my understanding here: For a struct, whether JSON or proto, the type if embedded, so when it is actually parsed, it becomes the real object? Is that right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The type doesn't appear in the config at all, all we have is a google.protobuf.Struct
knowledge when we encounter an opaque config. We then convert this Struct
to JSON and then load it back into a Protobuf::Message
(superclass of all concrete proto types), by invoking Protobuf::util::JsonStringToMessage(json, &message)
. This function makes use of the type information in the supplied message
to figure out how to do the parsing of the JSON.
This is why we need createConfigProto()
, it's essentially providing a JSON->message conversion factory via the proto message it returns. If you can think of a cleaner way to do this, I'm open to that as well (we could literally renamed to createConfigProtoFactory
and give it some more abstract interface if you prefer).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this mean that HttpConnectionManager message actually never appears on the wire, but is used internally by Envoy only? I.e., to pass in http connection manager config via a protobuf I have to map from the HttpConnectionManager message definition to a protobuf Struct, field by field. If so, what is the point of specifying the HttpConnectionMananger message in the envoy-api?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could not figure it out, managed to get it to work by encoding to Struct, seems to work but looks quite inefficient coding-wise:
filter_chains {
filters {
name: "http_connection_manager"
config {
fields {
key: "rds"
value {
struct_value {
fields {
key: "config_source"
value {
struct_value {
fields {
key: "api_config_source"
value {
struct_value {
fields {
key: "api_type"
value {
number_value: 2
}
}
fields {
key: "cluster_name"
value {
string_value: "rdsCluster"
}
}
}
}
}
}
}
}
fields {
key: "route_config_name"
value {
string_value: "router1"
}
}
}
}
}
fields {
key: "stat_prefix"
value {
string_value: "proxy"
}
}
}
deprecated_v1 {
type: "read"
}
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it only appears as a Struct
. What you have is correct. The goal here is not efficiency, but being extensible, we don't want to predefine all filter config protos upfront, we want to support user extension. The alternative google.protobuf.Any
type was not attractive as it is hard to do anything with the encoded proto without the original schema, e.g. dumping debug, assembling in config generation tools.
This is why we use JSON/YAML for textual representation, the ugliness of Struct
is hidden.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm interested in understanding the efficiency requirements here. Two questions:
- Given this is control plane, I wouldn't normally be that concerned about slight encoding inefficiency. Do you have some very significant scaling requirements in terms of the size/frequency of config update?
- I haven't looked at the wire format for Struct, but I assume the main objection is the fact that the key string is included, which is not needed if you know the proto upfront.
TBH, this is part of the API which we've tentatively frozen, so it would take a compelling reason to make this change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't have any numbers now, but you are likely right in that performance difference does not matter here. Even so it seems to me that there is much bigger difference in encoding/decoding CPU/memory use than in the wire format. Given that one of the main rationales of protobufs vs. XML (for example) is >10x encoding/decoding performance difference this seems like going backwards.
On the second point the change could be made backwards compatible by keeping the code point 2 for Struct config, and adding new code point(s) for Any or Oneof Message configs?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the best thing to do at this point is open an issue and let's see what others in the community think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See #1680.
test/integration/server.h
Outdated
Stats::CounterSharedPtr counter(const std::string& name) { | ||
// When using the thread local store, only counters() is thread safe. This also allows us | ||
// to test if a counter exists at all versus just defaulting to zero. | ||
return TestUtility::findCounter(*stat_store_, name); | ||
} | ||
|
||
Stats::GaugeSharedPtr gauge(const std::string& name) { | ||
return TestUtility::findGauge(*stat_store_, name); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Can we add variant of comment in counter() to here about thread safety.
include/envoy/server/filter_config.h
Outdated
* config will be parsed into the result of this method. Optional today, will be | ||
* compulsory when v1 is deprecated. | ||
*/ | ||
virtual std::unique_ptr<Protobuf::Message> createConfigProto() { return nullptr; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry I'm a little confused. I'm trying to understand why we need a createConfigProto() function in addition to the new style factory function. (I think I'm getting mixed up with how we are going from proto to JSON and then back to proto, etc.). I assume this is because we need a well typed structure to jam JSON into, but isn't that somehow known from the type inside it? Is there any way to make this more clear?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What you say above is correct, except for the bit about the "type inside". There's no implied type in a google.protobuf.Struct
. If we had gone down the route of google.protobuf.Any
, there would be a type URL present in the JSON that we could use to perform this conversion without the filter having to supply this factory (see the other comment on this topic for more discussion of JSON->message).
rds: | ||
routeConfigName: route_config_0 | ||
configSource: { path: {{ rds_json_path }} } | ||
httpFilters: [{ name: router, config: { deprecatedV1: true }}] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
from json to yaml? and user configs have to mention these weird strings
"@type": type.googleapis.com/envoy.api.v2.Listener
? :(
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure what the "from json to yaml" is referring to, can you clarify?
On the @type
annotations, these are a product of the generic handling of subscription types and ADS multiplexing support. I don't expect we'll see this stuff in hand-written configs in v2, instead, we will allow the bootstrap to specify both listeners and clusters, like v1, so this should only show up in test files like the above, or where folks want filesystem based dynamic config. I agree it's a bit ugly, but hand-written configs are really only supported in a best effort manner, one of the v2 API principles is that the API is primarily for machines.
include/envoy/server/filter_config.h
Outdated
* config will be parsed into the result of this method. Optional today, will be | ||
* compulsory when v1 is deprecated. | ||
*/ | ||
virtual ProtobufTypes::MessagePtr createConfigProto() { return nullptr; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per offline convo I think we decided to switch to createEmptyConfigProto() and have a slightly more robust comment about struct -> JSON -> message. Otherwise change LGTM.
Signed-off-by: Shriram Rajagopalan <[email protected]>
Description: - Connection classes will open minimum of 2 under most circumstances to a given endpoint and distribute requests between them (previously, only 1). - Fixes platform-bridged filters crash when resumed asynchronously after stream termination. - Adds Pulse support for stats tags. - Enables configuration of stream idle timeout. - Disables route timeout by default. - Introduces a Python interface compatible with the popular Requests library. - Adds experimental QUIC integration test. - Adds pure JVM support. Signed-off-by: Mike Schore <[email protected]> Signed-off-by: JP Simard <[email protected]>
Description: - Connection classes will open minimum of 2 under most circumstances to a given endpoint and distribute requests between them (previously, only 1). - Fixes platform-bridged filters crash when resumed asynchronously after stream termination. - Adds Pulse support for stats tags. - Enables configuration of stream idle timeout. - Disables route timeout by default. - Introduces a Python interface compatible with the popular Requests library. - Adds experimental QUIC integration test. - Adds pure JVM support. Signed-off-by: Mike Schore <[email protected]> Signed-off-by: JP Simard <[email protected]>
…n test.
This PR provides an integration test demoing LDS/RDS/CDS/EDS. Along the way, needed to solve the
problem of plumbing in the new HTTP connection manager filter proto config, rather than the legacy
v1 JSON. The solution adopted in this PR is to use a "deprecatedV1" field in the opaque config to
determine if we're doing legacy config or v2 proto.